#include "gui.h"
#include "gui_button.h"
#include "gui_surface.h"
#include "gui_container.h"
#include "gui_signal.h"
#include "gui_scrollbar.h"
#include <assert.h>
#include <stdlib.h>

static void gui_scrollbar_up_btn_event(gui_scrollbar_t *scrollbar);
static void gui_scrollbar_donw_btn_event(gui_scrollbar_t *scrollbar);

static void gui_scrollbar_calc_pos_float_size(gui_scrollbar_t *scrollbar, int show_w, int show_h, int *pos_float_w, int *pos_float_h)
{
    int step;
    int pos_h = scrollbar->scroll_spirit.height;
    int pos_w = scrollbar->scroll_spirit.width;
    int type = scrollbar->type;
    int width = scrollbar->width;
    int height = scrollbar->height;

    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        step = width / show_w;
        *pos_float_w = (width / step) - (width - show_w / step);
        *pos_float_h = pos_h;
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            step = height / show_h;
            *pos_float_w = pos_w;
            *pos_float_h = (height / step) - (height - show_h / step);
        }
    }
}

static void gui_scrollbar_calc_step(gui_scrollbar_t *scrollbar, int *step)
{
    int type=scrollbar->type;
    int width = scrollbar->width, height = scrollbar->height;
    int show_width = scrollbar->show_width, show_height = scrollbar->show_height;

    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        *step = width / show_width;
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            *step = height / show_height;
        }
    }
}

gui_spirit_t *gui_scrollbar_create_default(int width, int height, int show_w, int show_h, gui_scrollbar_type_t type)
{
    gui_spirit_t *spirit = NULL;

    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        spirit = gui_scrollbar_create(width, height, show_w, show_h, show_w, 32, type);
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            spirit = gui_scrollbar_create(width, height, show_w, show_h, 32, show_h, type);
        }
    }
    return spirit;
}

// create a scrollbar
/*  width: scroll area actual width
    height: scroll area actual height
    show_width: visable area width
    show_height: visable area height
    scroll_w: bar width
    scroll_h: bar height
    type:   scrollbar type
*/
gui_spirit_t *gui_scrollbar_create(int width, int height, int show_w, int show_h, int scroll_w, int scroll_h, gui_scrollbar_type_t type)
{
    gui_scrollbar_t *scrollbar = malloc(sizeof(gui_scrollbar_t));
    assert(scrollbar);

    dprintf("[gui_scrollbar] create scrollbar type %d w %d h %d show w %d show h %d\n",type,width,height,show_w,show_h);

    scrollbar->type = type;
    scrollbar->width = width;
    scrollbar->height = height;
    scrollbar->show_width = show_w;
    scrollbar->show_height = show_h;
    scrollbar->cur_pos=0;
    scrollbar->step=0;

    int w, h;
    int pos_w, pos_h;
    int step;
    int pos_float_w, pos_float_h;
    int btn_w,btn_h;

    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        w = show_w;
        h = show_h+scroll_h; 
        btn_w=8;
        btn_h=scroll_h;
        pos_w = scroll_w-2*btn_w;
        pos_h = scroll_h;
        step = width / show_w;
        pos_float_w = (width / step) - (show_w / step);
        pos_float_h = scroll_h;
        dprintf("[scroll_ltor] w %d h %d posw %d posh %d step %d float size %d %d\n",w,h,pos_w,pos_h,step,pos_float_w,pos_float_h);
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            w = show_w+scroll_w;
            h = show_h;
            btn_w=scroll_w;
            btn_h=8;
            pos_w = scroll_w;
            pos_h = scroll_h-2*btn_h;
            step = height / show_h;
            pos_float_w = scroll_w;
            pos_float_h = (height / step) - ( show_h / step);
            dprintf("[scroll_ttob] w %d h %d posw %d posh %d step %d float size %d %d\n",w,h,pos_w,pos_h,step,pos_float_w,pos_float_h);
        }
    }
    scrollbar->pos_float_width = pos_float_w;
    scrollbar->pos_float_height = pos_float_h;
    scrollbar->step=step;
    dprintf("pos float size %d %d step %d\n",pos_float_w,pos_float_h,scrollbar->step);

    // create spirit
    gui_spirit_t *spirit = &scrollbar->spirit;
    gui_spirit_init(spirit,0, 0, w, h);
    spirit->type = GUI_SPIRIT_TYPE_SCROLLBAR; // set spirit type
    spirit->style.background_color = GUI_GREEN;
    spirit->container = gui_container_create(spirit);
    if(!spirit->container)
        goto err;
    spirit->surface=gui_surface_create(w,h);
    if(!spirit->surface)
        goto err_spirit_surface;


    gui_spirit_t *scroll_spirit = &scrollbar->scroll_spirit;
    gui_spirit_init(scroll_spirit,0, 0, scroll_w, scroll_h);
    scroll_spirit->style.background_color = GUI_COLOR_NONE;
    scroll_spirit->container = gui_container_create(scroll_spirit);
    if(!scroll_spirit->container)
        goto err_scroll_container;

    gui_spirit_t *window_spirit = &scrollbar->window_spirit;
    gui_spirit_init(window_spirit,0, 0, width, height);
    window_spirit->style.background_color = GUI_COLOR_NONE;
    window_spirit->container = gui_container_create(window_spirit);
    if(!window_spirit->container)
        goto err_window_container;

    gui_spirit_t *show_spirit = &scrollbar->show_spirit;
    gui_spirit_init(show_spirit,0, 0, show_w, show_h);
    show_spirit->style.background_color = GUI_COLOR_NONE;
    show_spirit->container = gui_container_create(show_spirit);
    if(!show_spirit->container)
        goto err_show_container;
    show_spirit->surface=gui_surface_create(show_w,show_h);
    if(!show_spirit->surface)
        goto err_show_surface;

    // create up button
    gui_spirit_t **up_btn = &scrollbar->up_btn;
    *up_btn = gui_button_create();
    if(!*up_btn)
        goto err_up_btn;
    (*up_btn)->style.background_color = GUI_WHITE;
    gui_spirit_reset_size(*up_btn,btn_w,btn_h);
    // create down button
    gui_spirit_t **down_btn = &scrollbar->down_btn;
    *down_btn = gui_button_create();
    if(!*down_btn)
        goto err_down_btn;
    (*down_btn)->style.background_color = GUI_WHITE;
    gui_spirit_reset_size(*down_btn,btn_w,btn_h);

    // create pos spirit
    gui_spirit_t *pos_spirit = &scrollbar->pos_spirit;
    gui_spirit_init(pos_spirit,0, 0, pos_w, pos_h);
    pos_spirit->style.background_color = GUI_YELLOW;
    pos_spirit->surface=gui_surface_create(pos_w,pos_h);
    if(!pos_spirit->surface)
        goto err_pos_surface;

    // redirect
    int scroll_x, scroll_y;
    int up_x, up_y, down_x, donw_y;
    int pos_x, pos_y;
    int window_x, window_y;
    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        scroll_x = 0;
        scroll_y = height;
        window_x = 0;
        window_y = 0;
        up_x = 0;
        up_y = 0;
        down_x = up_x+scroll_w-btn_w-1;
        donw_y = 0;
        pos_x = btn_w;
        pos_y = 0;
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            scroll_x = width;
            scroll_y = 0;
            window_x = 0;
            window_y = 0;
            up_x = 0;
            up_y = 0;
            down_x = 0;
            donw_y = up_y+scroll_h-btn_h-1;
            pos_x = 0;
            pos_y = btn_h;
        }
    }
    // set window container pos
    window_spirit->x = 0;
    window_spirit->y = 0;
    // show spirit pos
    show_spirit->x = window_x;
    show_spirit->y = window_y;
    // set scroll widget contaienr pos
    scroll_spirit->x = scroll_x;
    scroll_spirit->y = scroll_y;
    // scroll sub spirit pos
    (*up_btn)->x = up_x;
    (*up_btn)->y = up_y;
    pos_spirit->x = pos_x;
    pos_spirit->y = pos_y;
    (*down_btn)->x = down_x;
    (*down_btn)->y = donw_y;

    // add to container
    gui_container_add(GUI_CONTAINER(spirit), show_spirit);
    gui_container_add(GUI_CONTAINER(spirit), scroll_spirit);
    gui_container_add(GUI_CONTAINER(scroll_spirit), *up_btn);
    gui_container_add(GUI_CONTAINER(scroll_spirit), *down_btn);
    gui_container_add(GUI_CONTAINER(scroll_spirit), pos_spirit);
    
    // set button signal
    /*gui_signal_connect(&up_btn, "button_press", gui_scrollbar_up_btn_event, scrollbar);
    gui_signal_connect(&up_btn, "button_press", gui_scrollbar_donw_btn_event, scrollbar);
    */

    dprintf("[gui scrollbar] create scrollbar ok\n");

    return spirit;
    err_pos_surface:
        gui_spirit_destroy(*down_btn);
    err_down_btn:
        gui_spirit_destroy(*up_btn);
    err_up_btn:
        gui_surface_destroy(show_spirit->surface);
    err_show_surface:
        gui_container_destroy(show_spirit->container);
    err_show_container:
        gui_container_destroy(window_spirit->container);
    err_window_container:
        gui_container_destroy(scroll_spirit->container);
    err_scroll_container:
        gui_container_destroy(spirit->surface);
    err_spirit_surface:
        gui_container_destroy(spirit->container);
    err:
        return NULL;
}

void gui_scrollbar_show(gui_scrollbar_t *scrollbar)
{
    gui_spirit_show(&scrollbar->spirit);
    gui_spirit_show_children(&scrollbar->scroll_spirit);
    gui_scrollbar_draw_pos_float(scrollbar);
}

 void gui_scrollbar_up_btn_event(gui_scrollbar_t *scrollbar)
{
    scrollbar->cur_pos += 1;
    gui_scrollbar_draw_pos_float(scrollbar);
    gui_scrollbar_flush_show(scrollbar);
    gui_scrollbar_update(scrollbar);
}

 void gui_scrollbar_down_btn_event(gui_scrollbar_t *scrollbar)
{
    scrollbar->cur_pos -= 1;
    gui_scrollbar_draw_pos_float(scrollbar);
    gui_scrollbar_flush_show(scrollbar);
    gui_scrollbar_update(scrollbar);
}

void gui_scrollbar_mouse_press(gui_scrollbar_t *scrollbar, int mouse_x, int mouse_y)
{
    int type = scrollbar->type;
    int cur_pos = scrollbar->cur_pos;

    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        int pos_x = scrollbar->pos_spirit.x - scrollbar->spirit.x;

        if (mouse_x >= pos_x && mouse_x <= scrollbar->pos_spirit.width) // mouse pos in float area
        {
            if (!scrollbar->start_scroll) // no scroll
            {
                if (mouse_x - pos_x >= cur_pos && mouse_x - pos_x <= scrollbar->pos_float_width) // mouse in float block
                {
                    scrollbar->start_scroll = 1;
                    scrollbar->start_pos = mouse_x - pos_x; // relate to scroll spirit
                }
            }
        }
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            int pos_y = scrollbar->pos_spirit.y - scrollbar->spirit.y;
            if (mouse_y >= pos_y && mouse_y <= scrollbar->pos_spirit.height) // mouse pos in float area
            {
                if (!scrollbar->start_scroll)
                {
                    if (mouse_y - pos_y >= cur_pos && mouse_y - pos_y <= scrollbar->pos_float_height)
                    {
                        scrollbar->start_scroll = 1;
                        scrollbar->start_pos = mouse_y - pos_y; // relate to scroll spirit
                    }
                }
            }
        }
    }
}

void gui_scrollbar_mouse_release(gui_scrollbar_t *scrollbar, int mouse_x, int mouse_y)
{
    int type = scrollbar->type;
    if (type = GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        int pos_x = scrollbar->pos_spirit.x - scrollbar->spirit.x;

        if (mouse_x >= pos_x && mouse_x <= pos_x + scrollbar->pos_spirit.width) // mouse pos in float area
        {
            int start_pos = scrollbar->start_pos;
            int end_pos = scrollbar->end_pos;
            int scroll_size = end_pos - start_pos;
            int float_x = start_pos + scroll_size;
            int float_y = 0;

            if (scrollbar->start_scroll) // in scroll status
            {
                scrollbar->start_scroll = 0;

                scrollbar->cur_pos = float_x;
                // flush scroll window and spirit
                gui_scrollbar_scroll(scrollbar);
            }
        }
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            int pos_y = scrollbar->pos_spirit.y - scrollbar->spirit.y;

            if (mouse_y >= pos_y && mouse_y <= pos_y + scrollbar->pos_spirit.height) // mouse in float area
            {
                int start_pos = scrollbar->start_pos;
                int end_pos = scrollbar->end_pos;
                int scroll_size = end_pos - start_pos;
                int float_x = 0;
                int float_y = start_pos + scroll_size;

                if (scrollbar->start_scroll)
                    scrollbar->start_scroll = 0;

                scrollbar->cur_pos = float_y;
                // flush scroll window and spirit
                gui_scrollbar_scroll(scrollbar);
            }
        }
    }
}

void gui_scrollbar_mouse_motion(gui_scrollbar_t *scrollbar, int mouse_x, int mouse_y)
{
    int type = scrollbar->type;
    int cur_pos = scrollbar->cur_pos;

    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        int pos_x = scrollbar->pos_spirit.x - scrollbar->spirit.x;

        if (mouse_x >= pos_x && mouse_x <= pos_x + scrollbar->pos_spirit.width) // mouse in float area
        {
            if (scrollbar->start_scroll)
            {
                int start_pos = scrollbar->start_pos;
                int end_pos = mouse_x - pos_x;
                int scroll_size = end_pos - start_pos;
                int float_x = cur_pos + scroll_size;
                int float_y = 0;

                scrollbar->cur_pos = float_x;
                // flush scroll area
                gui_scrollbar_scroll(scrollbar);

                // update float start pos for next scroll
                scrollbar->start_pos = mouse_x - scrollbar->pos_spirit.x;
            }
        }
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            int pos_y = scrollbar->pos_spirit.y - scrollbar->spirit.y;

            if (mouse_y >= pos_y && mouse_x <= pos_y + scrollbar->pos_spirit.width)
            {
                if (scrollbar->start_scroll)
                {
                    int start_pos = scrollbar->start_pos;
                    int end_pos = mouse_y - pos_y;
                    int scroll_size = end_pos - start_pos;
                    int float_x = 0;
                    int float_y = cur_pos + scroll_size;

                    scrollbar->cur_pos = float_y;
                    // flush scroll area
                    gui_scrollbar_scroll(scrollbar);

                    // update float start pos for next scroll
                    scrollbar->start_pos = mouse_x - scrollbar->pos_spirit.x;
                }
            }
        }
    }
}

void gui_scrollbar_update(gui_scrollbar_t *scrollbar)
{
    gui_spirit_show_all(&scrollbar->show_spirit);
}

// flush cur pos all spirit to show container
void gui_scrollbar_flush_show(gui_scrollbar_t *scrollbar)
{
    int pos = scrollbar->cur_pos;
    int type = scrollbar->type;
    int step = scrollbar->step;

    // get flush pos
    int flush_x, flush_y;
    int flush_width, flush_height;
    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        flush_x = pos * step;
        flush_y = 0;
        flush_width = scrollbar->show_width - flush_x;
        flush_height = scrollbar->show_height;
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            flush_x = 0;
            flush_y = pos * step;
            flush_width = scrollbar->show_width;
            flush_height = scrollbar->show_height - flush_y;
        }
    }

    // copy spirit to show container
    gui_container_t *src_container = &scrollbar->window_spirit;
    gui_container_t *dst_container = &scrollbar->show_spirit;
    assert(!src_container);
    assert(!dst_container);

    // clear dst container then receive new spirit
    gui_container_remove_destroy_all(dst_container);

    gui_spirit_t *spirit;
    int spirit_new_x, spirit_new_y, spirit_new_w, spirit_new_h;

    list_traversal_all_owner_to_next(spirit,&src_container->children_list,list)
    {
        int spirit_src_x = spirit->x - src_container->spirit->x, spirit_src_y = spirit->y - src_container->spirit->y; // figrue out spirit pos

        // spirit fully in flush area
        if ((spirit_src_x >= flush_x && spirit_src_y >= flush_y) && (spirit->width == flush_width && spirit->height == flush_height))
        {
            spirit_new_x = spirit_src_x - flush_x;
            spirit_new_y = spirit_src_y - flush_y;
            spirit_new_w = spirit->width;
            spirit_new_h = spirit->height;
        }
        else
        {
            // spirit start pos in flush area
            if ((spirit_src_x <= flush_x && spirit_src_y <= spirit->width) && (spirit->width > flush_width || spirit->height > flush_height))
            {
                spirit_new_x = spirit_src_x - flush_x;
                spirit_new_y = spirit_src_y - flush_y;
                spirit_new_w = spirit->width - flush_width;
                spirit_new_h = spirit->height - flush_height;
            }
            else
            {
                // spirit end pos in flush area
                if ((spirit_src_x < flush_x || spirit_src_y < flush_y) && (spirit->width < flush_width && spirit->height < flush_height))
                {
                    spirit_new_x = flush_x;
                    spirit_new_y = flush_y;
                    spirit_new_w = flush_width;
                    spirit_new_h = flush_height;
                }
                else
                {
                    // whole spirit no in flush area
                    if ((spirit_src_x < flush_x || spirit_src_y < flush_y) && (spirit->width > flush_width || spirit->height > flush_height))
                    {
                        spirit_new_x = flush_x;
                        spirit_new_y = flush_y;
                        spirit_new_w = flush_width;
                        spirit_new_h = flush_height;
                    }
                }
            }
        }

        // copy spirit and change pos to adapt to new container
        gui_spirit_t *copy_spirit = malloc(sizeof(gui_spirit_t));
        memcpy(copy_spirit, spirit, sizeof(gui_spirit_t));
        copy_spirit->x = spirit_new_x;
        copy_spirit->y = spirit_new_y;
        copy_spirit->width = spirit_new_w;
        copy_spirit->height = spirit_new_h;
        gui_container_add(dst_container, copy_spirit);
    }
}

// scrollbar draw scroll result to screen
void gui_scrollbar_scroll(gui_scrollbar_t *scrollbar)
{
    gui_spirit_show(&scrollbar->pos_spirit); // draw float block
    gui_scrollbar_flush_show(scrollbar);
    gui_scrollbar_update(scrollbar);
}

// draw pos float spirit to screen
void gui_scrollbar_draw_pos_float(gui_scrollbar_t *scrollbar)
{
    int pos = scrollbar->cur_pos;
    int step = scrollbar->step;
    int type = scrollbar->type;
    gui_surface_t *surface = scrollbar->pos_spirit.surface;

    // create new surface
    if (!surface)
    {
        surface = gui_surface_create(scrollbar->pos_spirit.width, scrollbar->pos_spirit.height);
        assert(surface);
    }

    dprintf("[scrollbar] type %d float pos %d step %d\n",type,pos,step);

    if (type == GUI_SCROLLBAR_LEFT_TO_RIGHT)
    {
        gui_surface_rectfill(surface, pos * step, 0, scrollbar->pos_float_width, scrollbar->pos_float_height, GUI_BLACK);
    }
    else
    {
        if (type == GUI_SCROLLBAR_TOP_TO_BOTTOM)
        {
            gui_surface_rectfill(surface, 0, pos * step, scrollbar->pos_float_width, scrollbar->pos_float_height, GUI_BLACK);
        }
    }
    dprintf("[scrollbar] draw float blocked ok\n");
}

void gui_scrollbar_add_spirit(gui_scrollbar_t *scrollbar, gui_spirit_t *spirit)
{
    gui_container_add(GUI_CONTAINER(&scrollbar->window_spirit), spirit);
    // flush show area to current pos
    gui_scrollbar_flush_show(scrollbar);
    gui_scrollbar_update(scrollbar);
}

void gui_scrollbar_remove_spirit(gui_scrollbar_t *scrollbar, gui_spirit_t *spirit)
{
    gui_container_remove(GUI_CONTAINER(&scrollbar->window_spirit), spirit);
    // flush show area to curretn pos
    gui_scrollbar_flush_show(scrollbar);
    gui_scrollbar_update(scrollbar);
}

void gui_scrollbar_set_show_size(gui_scrollbar_t *scrollbar, int show_w, int show_h)
{
    scrollbar->show_width = show_w;
    scrollbar->show_height = show_h;

    gui_spirit_set_size(&scrollbar->show_spirit, show_w, show_h);
    gui_scrollbar_calc_step(scrollbar, &scrollbar->step);
    gui_scrollbar_calc_pos_float_size(scrollbar, show_w, show_h, &scrollbar->pos_float_width, &scrollbar->pos_float_height);

    // reflush scrollbar show area
    gui_scrollbar_draw_pos_float(scrollbar);
    gui_scrollbar_flush_show(scrollbar);
    gui_scrollbar_update(scrollbar);
}

void gui_scrollbar_set_window_size(gui_scrollbar_t *scrollbar, int window_w, int window_h)
{
    if (!scrollbar)
        return;

    scrollbar->width = window_w;
    scrollbar->height = window_h;

    gui_spirit_set_size(&scrollbar->window_spirit, window_w, window_h);
    gui_scrollbar_calc_step(scrollbar, &scrollbar->step);
}

void gui_scrollbar_set_step(gui_scrollbar_t *scrollbar, int step)
{
    if (!scrollbar)
        return;
    scrollbar->step = step;
}

void gui_scrollbar_get_step(gui_scrollbar_t *scrollbar, int *step)
{
    if (!scrollbar)
        return;

    *step = scrollbar->step;
}

void gui_scrollbar_get_window_size(gui_scrollbar_t *scrollbar, int *window_w, int *window_h)
{
    if (!scrollbar || !window_w || !window_h)
        return;
    *window_w = scrollbar->width;
    *window_h = scrollbar->height;
}

void gui_scrollbar_get_show_size(gui_scrollbar_t *scrollbar, int *show_w, int *show_h)
{
    if (!scrollbar || !show_w || !show_h)
        return;
    *show_w = scrollbar->show_width;
    *show_w = scrollbar->show_height;
}

void gui_scrollbar_scroll_window(gui_scrollbar_t *scrollbar, int step)
{
    scrollbar->cur_pos += step;      // update float pos
    gui_scrollbar_scroll(scrollbar); // flush scroll window
}

void gui_scrollbar_set_show_spirit(gui_scrollbar_t *scrollbar, gui_spirit_t *show_spirit)
{
    gui_spirit_t *spirit = &scrollbar->show_spirit;
    spirit = show_spirit;
    // get new step
    gui_scrollbar_calc_step(scrollbar, &scrollbar->step);
    // get pos float block size
    gui_scrollbar_calc_pos_float_size(scrollbar, show_spirit->width, show_spirit->height, &scrollbar->pos_float_width, &scrollbar->pos_float_height);
    // flush scroll show area
    gui_scrollbar_scroll(scrollbar);
}